<?php
/**
 * Nweb Framework
 *
 * This script is protected by copyright. It's use, copying, modification
 * and distribution without written consent of the author is prohibited.
 *
 * @category    source
 * @package     pl.nweb.framework.ftp
 * @author      Krzysztof Kardasz <krzysztof.kardasz@gmail.com>
 * @copyright   Copyright (c) 2011 Krzysztof Kardasz
 * @license     http://www.gnu.org/licenses/lgpl-3.0.txt  GNU Lesser General Public
 * @version     3.0 $Id$
 * @link        http://code.google.com/p/nweb-framework/
 * @link        http://framework.nweb.pl
 */

namespace pl\nweb\framework\ftp;

/**
 * Klient FTP
 *
 * @category    source
 * @package     pl.nweb.framework.ftp
 * @author      Krzysztof Kardasz <krzysztof.kardasz@gmail.com>
 * @copyright   Copyright (c) 2011 Krzysztof Kardasz
 * @version     3.0 $Id$
 */
class Client implements \RecursiveIterator
{
    /**
     * ASCII transfer mode
     */
    const MODE_ASCII = FTP_ASCII;

    /**
     * Binary transfer mode
     */
    const MODE_BINARY = FTP_BINARY;

    /**
     * Typy systemu
     */
    const SYSTEM_TYPE_UNIX       = 'UNIX';
    const SYSTEM_TYPE_WINDOWS_NT = 'WINDOWS_NT';

    /**
     * The FTP connection
     *
     * @var resource
     */
    protected $_connection = null;

    /**
     * The FTP host
     *
     * @var string
     */
    protected $_host = null;

    /**
     * The FTP username
     *
     * @var string
     */
    protected $_username = null;

    /**
     * The FTP password
     *
     * @var string
     */
    protected $_password = null;

    /**
     * The FTP port
     *
     * @var int
     */
    protected $_port = 21;

    /**
     * The command timeout
     *
     * @var int
     */
    protected $_timeout = 90;

    /**
     * The current transfer mode
     *
     * @var int
     */
    protected $_currentMode = self::MODE_BINARY;

    /**
     * The current path
     *
     * @var string
     */
    protected $_currentPath = null;

    /**
     *
     * @var \pl\nweb\framework\ftp\Client_Directory
     */
    protected $_currentDir = null;

    /**
     *
     * @var \pl\nweb\framework\ftp\Client_Directory
     */
    protected $_rootDir = null;

    /**
     *
     * @var string
     */
    protected $_sysType = null;

    /**
     * Whether or not to use passive mode
     *
     * @var boolean
     */
    protected $_passive = false;

    /**
     * Whether or not to use an SSL connection
     *
     * @var boolean
     */
    protected $_ssl = false;

    /**
     * Konstruktor
     *
     * @param string $host FTP host
     * @param string $username nazwa użytkownika
     * @param string $password hasło
     * @param int $port [opcjonalnie] port
     * @param int $timeout [opcjonalnie] timeout
     */
    public function __construct($host, $username, $password, $port = 21, $timeout = 90)
    {
        $this->_host     = $host;
        $this->_username = $username;
        $this->_password = $password;
        $this->_port     = $port;
        $this->_timeout  = $timeout;
    }

    /**
     * Zwraca flagę czy serwer jest połączony
     *
     * @return boolean
     */
    public function isConnected()
    {
        return null === $this->_connection;
    }

    /**
     * Połączenie z serwerem ftp
     *
     * @return void
     */
    public function connect()
    {
        $this->_connect();
    }

    /**
     * Zwraca połączenie z serwerem ftp
     *
     * @return resource
     */
    public function getConnection()
    {
        $this->_connect();
        return $this->_connection;
    }

    /**
     * Definiuje timeout
     *
     * @param int $timeout timeout
     * @return \pl\nweb\framework\ftp\Client
     * @throws Exception
     */
    public function setTimeout($timeout)
    {
        $this->_timeout = $timeout;
        if ($this->_connection !== null) {
            $option = @ftp_set_option($this->_connection, FTP_TIMEOUT_SEC, $this->_timeout);
            if ($option === false) {
                throw new Exception(
                    Exception::MSG_SET_TIMEOUT_ERROR,
                    Exception::COD_SET_TIMEOUT_ERROR
                );
            }
        }
        return $this;
    }

    /**
     * Defniuje czy używać połączenia SSL
     *
     * @param boolean $ssl
     * @return \pl\nweb\framework\ftp\Client
     */
    public function setSecure($ssl = true)
    {
        $this->_ssl = $ssl;
        return $this;
    }

    /**
     * Definiuje tryb pasywny
     *
     * @param boolean $passive falga
     * @return \pl\nweb\framework\ftp\Client
     */
    public function setPassive($passive = true)
    {
        $this->_passive = $passive;
        $this->_setPassive();

        return $this;
    }

    /**
     * Wysyła polecenie PASV
     *
     * @return \pl\nweb\framework\ftp\Client
     * @throws Exception
     */
    protected function _setPassive()
    {
        if ($this->_connection !== null) {
            $pasv = @ftp_pasv($this->_connection, $this->_passive);
            if ($pasv === false) {
                throw new Exception(
                    Exception::MSG_SET_PASSIVE_ERROR,
                    Exception::COD_SET_PASSIVE_ERROR
                );
            }
        }

        return $this;
    }

    /**
     * Definiuje domyślny tryb transferu
     *
     * @param int $mode tryb transferu
     * @return \pl\nweb\framework\ftp\Client
     * @throws Exception
     */
    public function setMode($mode)
    {
        switch ($mode) {
            case self::MODE_ASCII:
            case self::MODE_BINARY:
                $this->_currentMode = $mode;
            break;
            default:
                throw new Exception(
                    Exception::MSG_UNKNOWN_MODE,
                    Exception::COD_UNKNOWN_MODE
                );
        }
        return $this;
    }

    /**
     * Zmienia uprawnienia plikowi lub katalogowi
     *
     * @param string $path ścieżka do pliku lub katalogu
     * @param int $permissions uprawnienia ósemkowo (np. 0777)
     * @return boolean
     */
    public function chmod($path, $permissions)
    {
        $this->_connect();
        $chmod = @ftp_chmod($this->_connection, $permissions, $path);
        return $chmod !== false;
    }

    /**
     * Połączenie z serwerem FTP
     *
     * @return void
     * @throws Exception
     */
    protected function _connect()
    {
        if ($this->_connection === null) {
            if ($this->_ssl) {
                $connection = @ftp_ssl_connect($this->_host, $this->_port, $this->_timeout);
            } else {
                $connection = @ftp_connect($this->_host, $this->_port, $this->_timeout);
            }
            if ($connection === false) {
                throw new Exception(
                    Exception::MSG_CONNECTION_ERROR,
                    Exception::COD_CONNECTION_ERROR,
                    array($this->_host, $this->_port)
                );
            }

            $this->_connection = $connection;

            $login = @ftp_login($this->_connection, $this->_username, $this->_password);
            if ($login === false) {
                throw new Exception(
                    Exception::MSG_LOGIN_ERROR,
                    Exception::COD_LOGIN_ERROR,
                    array($this->_username)
                 );
            }

            if ($this->_passive) {
                $this->_setPassive();
            }

            $this->getCurrentPath();
        }
    }

    /**
     * Zwraca typ systemu
     *
     * @return string (UNIX|WINDOWS_NT)
     */
    public function getSystemType ()
    {
        if ($this->_sysType === null) {
            $this->_sysType = strtoupper(ftp_systype($this->getConnection()));
        }
        return $this->_sysType;
    }

    /**
     * Zwraca aktualną ścieżkę
     *
     * @return string
     * @throws Exception
     */
    public function getCurrentPath ()
    {
        if (null === $this->_currentPath) {
            $this->_connect();
            $path = @ftp_pwd($this->_connection);
            if ($path === false) {
                throw new Exception(
                    Exception::MSG_PWD_ERROR,
                    Exception::COD_PWD_ERROR
                );
            }
            $this->_currentPath = $path;
            $this->_currentDir  = null;
        }
        return $this->_currentPath;
    }

    /**
     * Zmienia aktualny katalog
     *
     * @return boolean
     */
    public function changePath ($path)
    {
        $this->_connect();
        if (@ftp_chdir($this->_connection, $path)) {
            $this->_currentPath = null;
            return true;
        }
        return false;
    }

    /**
     * Zwraca obiekt katalogu
     *
     * @param string $path ścieżka zdalnego katalogu
     * @return null|\pl\nweb\framework\ftp\Client_Directory
     */
    public function getDir ($path)
    {
        try {
            $current = $this->getCurrentPath();
            if (@ftp_chdir($this->_connection, $path)) {
                @ftp_chdir($this->_connection, $current);
                return new Directory($this, $path);
            }
        } catch (Exception $e) {}

        return null;
    }

    /**
     * Zwraca obiekt aktualnego katalogu
     *
     * @return \pl\nweb\framework\ftp\Directory
     */
    public function getCurrentDir ()
    {
        if (null === $this->_currentDir) {
            $this->_currentDir = new Directory($this, $this->getCurrentPath());
        }
        return $this->_currentDir;
    }

    /**
     * Zwraca obiekt głównego katalogu
     *
     * @return \pl\nweb\framework\ftp\Directory
     */
    public function getRootDir ()
    {
        if (null === $this->_rootDir) {
            $this->_rootDir = new Directory($this, '/');
        }
        return $this->_rootDir;
    }

    /**
     * Tworzy katalog
     *
     * @param string $path ścieżka
     * @return boolean
     * @throws Exception
     */
    public function createDir($path)
    {
        if (!@ftp_mkdir($this->getConnection(), $path)) {
            throw new Exception(
                Exception::MSG_CREATE_DIR_ERROR,
                Exception::COD_CREATE_DIR_ERROR,
                array($path)
            );
        }
        return true;
    }

    /**
     * Usuwa katalog
     *
     * @param string $path pusty katalog
     * @return boolean
     * @throws Exception
     */
    public function deleteDir($path)
    {
        if (!@ftp_rmdir($this->getConnection(), $path)) {
            throw new Exception(
                Exception::MSG_DELETE_DIR_ERROR,
                Exception::COD_DELETE_DIR_ERROR,
                array($path)
            );
        }
        return true;
    }

    /**
     * Zmienia nazwę pliku
     *
     * @param string $filename plik źródłowy
     * @param string $newname nowa nazwa pliku
     * @return boolean
     * @throws array
     */
    public function getList($dir = null)
    {
        if(null === $dir) {
            $dir = $this->getCurrentPath();
        }
        $list = @ftp_nlist($this->getConnection(), $dir);
        if(false === $list) {
            throw new Exception(
                Exception::MSG_DIR_LIST_ERROR,
                Exception::COD_DIR_LIST_ERROR,
                array($dir)
            );
        }
        return $list;
    }

    /**
     * Zmienia nazwę pliku
     *
     * @param string $filename plik źródłowy
     * @param string $newname nowa nazwa pliku
     * @return boolean
     * @throws array
     */
    public function getRawList($dir = null)
    {
        if(null === $dir) {
            $dir = $this->getCurrentPath();
        }
        $list = @ftp_rawlist($this->getConnection(), $dir);
        if(false === $list) {
            throw new Exception(
                Exception::MSG_DIR_LIST_ERROR,
                Exception::COD_DIR_LIST_ERROR,
                array($dir)
            );
        }
        return $list;
    }

    /**
     * Zmienia nazwę pliku lub katalogu
     *
     * @param string $filename plik źródłowy
     * @param string $newname nowa nazwa pliku
     * @return boolean
     * @throws Exception
     */
    public function rename($path, $newname)
    {
        $this->_connect();
        if (!@ftp_rename($this->_connection, $path, $newname)) {
            throw new Exception(
                Exception::MSG_RENAME_ERROR,
                Exception::COD_RENAME_ERROR,
                array($path)
            );
        }
        return true;
    }

    /**
     * Usuwa plik
     *
     * @param string $path plik źródłowy
     * @return boolean
     * @throws Exception
     */
    public function deleteFile($path)
    {
        if (!@ftp_delete($this->getConnection(), $path)) {
            throw new Exception(
                Exception::MSG_DELETE_ERROR,
                Exception::COD_DELETE_ERROR,
                array($path)
            );
        }
        return true;
    }

    /**
     * Wysyła plik do wskazanego miejsca
     *
     * @param string $file plik źródłowy
     * @param null|string $destination katalog docelowy
     * @param integer $mode tryb FTP_ASCII|FTP_BINARY
     * @return boolean
     * @throws Exception
     */
    public function send($file, $destination = null, $mode = null)
    {
        if (!is_file($file)) {
            throw new Exception(
                Exception::MSG_IS_NOT_FILE,
                Exception::COD_IS_NOT_FILE,
                array($file)
            );
        }

        if (null === $destination) {
            $destination = rtrim($this->getCurrentPath(), DIRECTORY_SEPARATOR). DIRECTORY_SEPARATOR . basename($file);
        }

        if (null === $mode) {
            $mode = $this->_currentMode;
        }

        $results = false;
        $fp = fopen($file, 'r');

        if(@ftp_fput($this->getConnection(), $destination, $fp, $mode)) {
            $results = true;
        }
        fclose($fp);
        return $results;
    }

    /**
     * Pobiera plik zdalny do wskazanego miejsca
     *
     * @param string $source plik zdalny na serwerze ftp
     * @param string $destination plik docelowy
     * @param integer $mode tryb FTP_ASCII|FTP_BINARY
     * @return boolean
     * @throws Exception
     */
    public function receive($source, $destination, $mode = null)
    {
        if (null === $mode) {
            $mode = $this->_currentMode;
        }
        $fp = @fopen($destination, 'w');
        if(!$fp) {
            throw new Exception(
                Exception::MSG_WRITE_FILE_ERROR,
                Exception::COD_WRITE_FILE_ERROR,
                array($destination)
            );
        }
        if (!@ftp_fget($this->getConnection(), $fp, $source, $mode, 0)) {
            throw new Exception(
                Exception::MSG_DOWNLOAD_FILE_ERROR,
                Exception::COD_DOWNLOAD_FILE_ERROR,
                array($destination)
            );
        }
        return true;
    }

    /**
     * Zwraca flagę czy bierzący element jest katalogiem
     *
     * @return boolean
     */
    public function hasChildren()
    {
        return $this->getRootDir()->hasChildren();
    }

    /**
     * Zwraca element
     *
     * @return mixed
     */
    public function getChildren()
    {
        return $this->getRootDir()->getChildren();
    }

    /**
     * Przesunięcie wskaźnika kolekcji na początek
     *
     * @return void
     */
    public function rewind()
    {
        $this->getRootDir()->rewind();
    }

    /**
     * Zwraca bieżacy element kolekcji
     *
     * @return mixed|false
     */
    public function current()
    {
        return $this->getRootDir()->current();
    }

    /**
     * Zwraca bieżacy klucz elementu kolekcji
     *
     * @return string|integer
     */
    public function key()
    {
        return $this->getRootDir()->key();
    }

    /**
     * Przsuwa bieżący element do następnej pozycji
     *
     * @return false|mixed
     */
    public function next()
    {
        return $this->getRootDir()->next();
    }

    /**
     * Zwraca flagę czy element jest prawidłowy
     *
     * @return boolean
     */
    public function valid()
    {
        return $this->getRootDir()->valid();
    }

    /**
     * Destruktor
     *
     * @return void
     */
    public function __destruct()
    {
        if ($this->_connection !== null) {
            @ftp_close($this->_connection);
        }
    }
}